package com.kwamecorp.brunoperry.kwamelogo.engine;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

import com.kwamecorp.brunoperry.kwamelogo.R;
import com.kwamecorp.brunoperry.kwamelogo.engine.shaders.FragmentShader;
import com.kwamecorp.brunoperry.kwamelogo.engine.shaders.KCShader;

public class KCModel3D extends KC3DLoader
{
	private static final int POSITION_DATA_SIZE = 2;
	private static final int NORMAL_DATA_SIZE = 3;
	private static final int TEXTURE_COORDINATE_DATA_SIZE = 2;
	private static final int BYTES_PER_FLOAT = 4;
	private static final boolean INDEX_DATA = true;

	private FloatBuffer vertexBuffer;
	private ShortBuffer indexBuffer;
	private FloatBuffer uvBuffer;
	private FloatBuffer normalsBuffer;

	private int vertexShader;
	private int fragmentShader;
	private int programObject;

	private int positionHandle;
	private int texCoordHandle;
	private int MVPMatrixHandle;
	private int samplerHandler;
	private int positionOffset = 0;

	private int textureId;

	private float[] modelMatrix = new float[16];
	private float[] rotXMatrix = new float[16];
	private float[] rotYMatrix = new float[16];
	private float[] rotZMatrix = new float[16];

	private float rotX = 0;
	private float rotY = 1;
	private float rotZ = 0;

	public KCModel3D(Context context) {
		super(context);
	}

	public void draw(float[] mvpMatrix)
	{
		update();

		vertexBuffer.position(positionOffset);
		GLES20.glUseProgram(programObject);
		GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
		GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, uvBuffer);

		GLES20.glEnableVertexAttribArray(positionHandle);
		GLES20.glEnableVertexAttribArray(texCoordHandle);

		GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
		GLES20.glUniform1i(samplerHandler, 0);

		GLES20.glUniformMatrix4fv(MVPMatrixHandle, 1, false, mvpMatrix, 0);

		GLES20.glDrawElements(GLES20.GL_TRIANGLES, numVerticesIndices, GLES20.GL_UNSIGNED_SHORT, indexBuffer);
	}

	private void update()
	{
		Matrix.setIdentityM(modelMatrix, 0);
		Matrix.translateM(modelMatrix, 0, 0, 0, -5);
		Matrix.setRotateM(rotXMatrix, 0, rotX, 1, 0, 0);
		Matrix.setRotateM(rotYMatrix, 0, rotY, 0, 1, 0);
		Matrix.setRotateM(rotZMatrix, 0, rotZ, 0, 1, 0);
		Matrix.multiplyMM(modelMatrix, 0, rotXMatrix, 0, rotYMatrix, 0);
		Matrix.multiplyMM(modelMatrix, 0, modelMatrix, 0, rotZMatrix, 0);
	}

	public void init()
	{
		setupShaders();
		setupTextures();
		setupBuffers();
	}

	private void setupBuffers()
	{
		if (INDEX_DATA)
		{
			// uvs = indexData(normals, uvIndices);
			uvs = normals;
		}
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * BYTES_PER_FLOAT);
		vbb.order(ByteOrder.nativeOrder());
		vertexBuffer = vbb.asFloatBuffer();
		vertexBuffer.put(vertices).position(0);

		ByteBuffer ibb = ByteBuffer.allocateDirect(vertexIndices.length * 2);
		ibb.order(ByteOrder.nativeOrder());
		indexBuffer = ibb.asShortBuffer();
		indexBuffer.put(vertexIndices).position(0);

		ByteBuffer ubb = ByteBuffer.allocateDirect(uvs.length * BYTES_PER_FLOAT);
		ubb.order(ByteOrder.nativeOrder());
		uvBuffer = ubb.asFloatBuffer();
		uvBuffer.put(uvs).position(0);

		ByteBuffer nbb = ByteBuffer.allocateDirect(normals.length * BYTES_PER_FLOAT);
		nbb.order(ByteOrder.nativeOrder());
		normalsBuffer = nbb.asFloatBuffer();
		normalsBuffer.put(normals).position(0);
	}

	private void setupTextures()
	{
		textureId = createSimpleTexture2D(R.drawable.logo_map);
	}

	private void setupShaders()
	{
		// shader data loader
		KCShader shaderLoader = new KCShader(context);
		int[] compiled;
		// build vertexShader
		vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
		if (vertexShader == 0)
		{
			Log.d("KW_GL", "Error creating vert shader: " + GLES20.glGetShaderInfoLog(vertexShader));
			return;
		}

		GLES20.glShaderSource(vertexShader, shaderLoader.loadShaderFile(R.raw.vertex_shader));
		GLES20.glCompileShader(vertexShader);

		// check compile status
		compiled = new int[1];
		GLES20.glGetShaderiv(vertexShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
		if (compiled[0] == 0)
		{
			Log.d("KW_GL", "Error compiling vert shader: " + GLES20.glGetShaderInfoLog(vertexShader));
			GLES20.glDeleteShader(vertexShader);
			return;
		}

		// buidl fragment shader
		fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
		if (fragmentShader == 0)
		{
			Log.d("KW_GL", "Error creating frag shader: " + GLES20.glGetShaderInfoLog(fragmentShader));
			return;
		}

		// GLES20.glShaderSource(fragmentShader, fShaderStr);
		GLES20.glShaderSource(fragmentShader, FragmentShader.getShaderData());
		GLES20.glCompileShader(fragmentShader);

		// check compile status
		compiled = new int[1];
		GLES20.glGetShaderiv(fragmentShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
		if (compiled[0] == 0)
		{
			Log.d("KW_GL", "Error compiling frag shader: " + GLES20.glGetShaderInfoLog(fragmentShader));
			GLES20.glDeleteShader(fragmentShader);
			return;
		}

		// setup program and attach shaders
		int program = GLES20.glCreateProgram();
		if (program == 0)
		{
			Log.d("KW_GL", "Error creating program: " + GLES20.glGetShaderInfoLog(program));
			return;
		}
		GLES20.glAttachShader(program, vertexShader);
		GLES20.glAttachShader(program, fragmentShader);

		// Bind vPosition to attribute 0 and link the program
		GLES20.glBindAttribLocation(program, 0, "a_Position");
		GLES20.glLinkProgram(program);
		// Check link status
		GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, compiled, 0);

		if (compiled[0] == 0)
		{
			Log.d("KW_GL", "Error linking program: " + GLES20.glGetProgramInfoLog(program));
			GLES20.glDeleteProgram(program);
			return;
		}

		MVPMatrixHandle = GLES20.glGetUniformLocation(program, "u_MVPMatrix");
		positionHandle = GLES20.glGetAttribLocation(program, "a_Position");
		texCoordHandle = GLES20.glGetAttribLocation(program, "a_texCoord");

		samplerHandler = GLES20.glGetUniformLocation(program, "s_texture");

		programObject = program;
	}

	@Override
	protected void fileLoaded()
	{
		// Load shader data file
		listener.onComplete();

	}

	public float[] getModelMatrix()
	{
		return modelMatrix;
	}

	private int createSimpleTexture2D(int resourceId)
	{
		final int[] textureHandle = new int[1];

		GLES20.glGenTextures(1, textureHandle, 0);

		if (textureHandle[0] != 0)
		{
			final BitmapFactory.Options options = new BitmapFactory.Options();
			options.inScaled = false; // No pre-scaling

			// Read in the resource
			final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

			// Bind to the texture in OpenGL
			GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

			// Set filtering
			GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
			GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

			// Load the bitmap into the bound texture.
			GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

			// Recycle the bitmap, since its data has been loaded into OpenGL.
			bitmap.recycle();
		}

		if (textureHandle[0] == 0)
		{
			throw new RuntimeException("Error loading texture.");
		}

		return textureHandle[0];
	}

	public void setRotation(float angle, float xAxis, float yAxis, float zAxis)
	{
		if (xAxis == 1)
		{
			rotY = (angle / 2);
		}
		if (yAxis == 1)
		{
			rotX = (angle / 2);
		}
		if (zAxis == 1)
		{
			rotZ += angle;
		}
	}
}